﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Globalization;
using System.IO;
using System.Xml.Linq;
using Microsoft.Practices.Unity;
using VisualWip.Core;
using VisualWip.Core.Interfaces;

namespace VisualWip.Util
{
    [Export(typeof (IPersistanceTreeSerializer))]
    public class PersistanceTreeXmlSerializer : IPersistanceTreeSerializer
    {
        private readonly IUnityContainer _container;

        [ImportingConstructor]
        public PersistanceTreeXmlSerializer(IUnityContainer container)
        {
            _container = container;
        }

        #region IPersistanceTreeSerializer Members

        public void Save(Stream stream, PersistenceTree persistenceTree)
        {
            var root = new XElement(persistenceTree.Name);
            root.Add(new XElement("Type", persistenceTree.Type.AssemblyQualifiedName));
            AddProperties(persistenceTree, root);
            AddChildren(persistenceTree, root);
            var document = new XDocument();
            document.Add(root);
            document.Save(stream);
        }

        public PersistenceTree Load(Stream stream)
        {
            XDocument document = XDocument.Load(stream);

            XElement root = document.Root;
            XElement typeElement = root.Element("Type");
            string[] args =
                (typeElement != null ? typeElement.Value : typeof (Application).AssemblyQualifiedName).Split(',');
            string typeName = string.Format("{0}, {1}", args);
            var tree = new PersistenceTree(root.Name.LocalName, typeName, () => Create(typeName));
            ReadProperties(root.Elements("Property"), tree);
            ReadChildren(root.Elements("Child"), tree);
            
            return tree;
        }

        #endregion

        private void ReadChildren(IEnumerable<XElement> elements, PersistenceTree tree)
        {
            foreach (XElement element in elements)
            {
                string name = element.Element("Name").Value;
                string typeName = element.Element("Type").Value;
                var settingsDictionary = new PersistenceTree(name, typeName, () => Create(typeName));
                tree.AddChild(settingsDictionary);
                ReadProperties(element.Elements("Property"), settingsDictionary);
                ReadChildren(element.Elements("Child"), settingsDictionary);
            }
        }

        private void ReadProperties(IEnumerable<XElement> elements, PersistenceTree tree)
        {
            foreach (XElement element in elements)
            {
                string typeName = element.Element("Type") != null ? element.Element("Type").Value : string.Empty;
                string propertyName = element.Element("Name") != null ? element.Element("Name").Value : string.Empty;
                string value = element.Element("Value") != null ? element.Element("Value").Value : string.Empty;

                //TODO: Build responibilty chaine here
                if (typeName == "System.String")
                {
                    tree.Add(propertyName, value);
                    continue;
                }
                if (typeName == "System.Boolean")
                {
                    tree.Add(propertyName, bool.Parse(value));
                    continue;
                }
                if (typeName == "System.Guid")
                {
                    tree.Add(propertyName, Guid.Parse(value));
                    continue;
                }
                if (typeName == "System.Int32" || typeName == "System.Int64" || typeName == "System.Int16")
                {
                    tree.Add(propertyName, int.Parse(value));
                    continue;
                }

                if (typeName == "System.TimeSpan")
                {
                    tree.Add(propertyName, TimeSpan.Parse(value));
                    continue;
                }


                if (typeName == "System.Uri")
                {
                    tree.Add(propertyName, new Uri(value));
                    continue;
                }

                if (string.IsNullOrWhiteSpace(typeName))
                    tree.Add(propertyName, string.Empty);
                continue;

                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Type: {0} is not handled",
                                                          typeName));
            }
        }

        private void AddChildren(PersistenceTree tree, XElement parent)
        {
            foreach (PersistenceTree child in tree.Children)
            {
                var childElement = new XElement("Child", new XElement("Name", child.Name),
                                                new XElement("Type", child.Type.AssemblyQualifiedName));
                AddProperties(child, childElement);
                AddChildren(child, childElement);
                parent.Add(childElement);
            }
        }

        private void AddProperties(PersistenceTree tree, XElement parent)
        {
            foreach (var property in tree.Properties)
            {
                var propertyElement = new XElement("Property", new XElement("Name", property.Key),
                                                   new XElement("Type",
                                                                property.Value != null
                                                                    ? property.Value.GetType().FullName
                                                                    : string.Empty),
                                                   new XElement("Value",
                                                                property.Value != null
                                                                    ? property.Value.ToString()
                                                                    : string.Empty));
                parent.Add(propertyElement);
            }
        }

        private object Create(string typeName)
        {
            string shortTypeName = string.Format("{0}, {1}", typeName.Split(','));
            Type type = Type.GetType(shortTypeName);
            if (type == null)
                type = Type.GetType(typeName);
            return _container.Resolve(type);
        }
    }
}